import os
import threading
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from typing import List, Optional, Tuple
from datetime import datetime

from .config import load_config, save_config
from .models import AppConfig, Task
from .sync_engine import full_sync_once, count_source_files
from .realtime import start_realtime_sync


class AppState:
    def __init__(self, cfg: AppConfig) -> None:
        self.cfg = cfg
        self.running_threads: dict[int, threading.Event] = {}

    def is_running(self, task_id: int) -> bool:
        return task_id in self.running_threads


def _next_task_id(cfg: AppConfig) -> int:
    return max((t.id for t in cfg.tasks), default=0) + 1


def _find_task(cfg: AppConfig, task_id: int) -> Tuple[int, Optional[Task]]:
    for idx, t in enumerate(cfg.tasks):
        if t.id == task_id:
            return idx, t
    return -1, None


class TextLogger:
    def __init__(self, text: tk.Text) -> None:
        self.text = text

    def __call__(self, msg: str) -> None:
        ts = datetime.now().strftime("%H:%M:%S")
        line = f"[{ts}] {msg}"
        # print to console
        print(line)
        # append to GUI
        self.text.after(0, lambda: (self.text.insert(tk.END, line + "\n"), self.text.see(tk.END)))


def _task_values(t: Task) -> List[str]:
    return [
        str(t.id),
        t.name,
        t.source_dir,
        str(len(t.target_dirs)),
        "双向" if t.bidirectional else "单向",
        str(getattr(t, "synced_files", 0)),
        str(getattr(t, "total_files", 0)),
        "运行中" if t.running else "空闲",
    ]


def _select_task_id(tree: ttk.Treeview) -> Optional[int]:
    sel = tree.selection()
    if not sel:
        return None
    item = sel[0]
    try:
        return int(tree.item(item, "values")[0])
    except Exception:
        return None


def _add_or_edit_dialog(parent: tk.Tk, base: AppState, task: Optional[Task]) -> Optional[Task]:
    win = tk.Toplevel(parent)
    win.title("编辑任务" if task else "添加任务")
    win.grab_set()

    name_var = tk.StringVar(value=task.name if task else "新任务")
    src_var = tk.StringVar(value=task.source_dir if task else "")
    targets: List[str] = task.target_dirs[:] if task else []
    targets_var = tk.StringVar(value=targets)

    def choose_src():
        path = filedialog.askdirectory(parent=win)
        if path:
            src_var.set(path)

    ttk.Label(win, text="名称").grid(row=0, column=0, sticky="w", padx=6, pady=4)
    ttk.Entry(win, textvariable=name_var, width=40).grid(row=0, column=1, columnspan=2, padx=6, pady=4, sticky="we")

    ttk.Label(win, text="源目录").grid(row=1, column=0, sticky="w", padx=6, pady=4)
    ttk.Entry(win, textvariable=src_var, width=40).grid(row=1, column=1, padx=6, pady=4, sticky="we")
    ttk.Button(win, text="选择", command=choose_src).grid(row=1, column=2, padx=6, pady=4)

    ttk.Label(win, text="目标目录").grid(row=2, column=0, sticky="nw", padx=6, pady=4)

    list_frame = ttk.Frame(win)
    list_frame.grid(row=2, column=1, columnspan=2, padx=6, pady=4, sticky="we")
    list_frame.columnconfigure(0, weight=1)

    target_list = tk.Listbox(list_frame, listvariable=targets_var, selectmode=tk.EXTENDED, height=6)
    target_list.grid(row=0, column=0, rowspan=3, sticky="nsew")

    scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=target_list.yview)
    scrollbar.grid(row=0, column=1, rowspan=3, sticky="ns")
    target_list.configure(yscrollcommand=scrollbar.set)

    def add_target():
        path = filedialog.askdirectory(parent=win)
        if path:
            path = path.rstrip("/\\")
            if path in targets:
                messagebox.showerror("提示", "目录已存在")
                return
            targets.append(path)
            targets_var.set(targets)

    def remove_target():
        sel = list(target_list.curselection())
        if not sel:
            return
        for idx in sorted(sel, reverse=True):
            if 0 <= idx < len(targets):
                targets.pop(idx)
        targets_var.set(targets)

    ttk.Button(list_frame, text="添加目录", command=add_target).grid(row=0, column=2, padx=6, pady=2, sticky="we")
    ttk.Button(list_frame, text="删除所选", command=remove_target).grid(row=1, column=2, padx=6, pady=2, sticky="we")

    result: List[Optional[Task]] = [None]

    def on_ok():
        name = name_var.get().strip()
        src = src_var.get().strip()
        if not name or not src or not targets:
            messagebox.showerror("提示", "名称、源目录、目标目录均为必填")
            return
        new_task = Task(
            id=(task.id if task else _next_task_id(base.cfg)),
            name=name,
            source_dir=src,
            target_dirs=targets[:],
            bidirectional=(task.bidirectional if task else False),
            enabled=(task.enabled if task else True),
        )
        result[0] = new_task
        win.destroy()

    def on_cancel():
        win.destroy()

    btn_frame = ttk.Frame(win)
    btn_frame.grid(row=3, column=0, columnspan=3, pady=8)
    ttk.Button(btn_frame, text="保存", command=on_ok).pack(side=tk.LEFT, padx=6)
    ttk.Button(btn_frame, text="取消", command=on_cancel).pack(side=tk.LEFT, padx=6)

    win.columnconfigure(1, weight=1)
    win.wait_window()
    return result[0]


def show_main() -> None:
    cfg, cfg_path = load_config()
    state = AppState(cfg)

    root = tk.Tk()
    root.title("文件盘同步工具")
    root.geometry("900x520")

    # Controls
    columns = ("ID", "名称", "源目录", "目标数", "模式", "已同步", "总文件", "状态")
    tree = ttk.Treeview(root, columns=columns, show="headings")
    for col in columns:
        tree.heading(col, text=col)
        if col == "源目录":
            width = 260
        elif col in ("已同步", "总文件", "目标数"):
            width = 90
        else:
            width = 120
        tree.column(col, width=width, anchor="w")
    tree.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

    def refresh():
        unique: dict[int, Task] = {}
        for t in state.cfg.tasks:
            unique[t.id] = t
        if len(unique) != len(state.cfg.tasks):
            state.cfg.tasks = list(unique.values())
            save_config(state.cfg)
        for t in state.cfg.tasks:
            t.running = state.is_running(t.id)
        for item in tree.get_children():
            tree.delete(item)
        for t in state.cfg.tasks:
            tree.insert("", tk.END, values=_task_values(t))

    btn_bar = ttk.Frame(root)
    btn_bar.pack(fill=tk.X, padx=6)
    ttk.Button(btn_bar, text="➕ 添加任务", command=lambda: on_add()).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_bar, text="✏️ 编辑任务", command=lambda: on_edit()).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_bar, text="🗑️ 删除任务", command=lambda: on_delete()).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_bar, text="▶️ 开始同步", command=lambda: on_start()).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_bar, text="⏸️ 停止同步", command=lambda: on_stop()).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_bar, text="🔄 完整同步", command=lambda: on_full()).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_bar, text="⚙️ 设置", command=lambda: on_settings()).pack(side=tk.LEFT, padx=4)
    ttk.Label(btn_bar, text=f"配置: {cfg_path}").pack(side=tk.RIGHT)

    log_text = tk.Text(root, height=10)
    log_text.pack(fill=tk.BOTH, expand=False, padx=6, pady=6)
    logger = TextLogger(log_text)

    def record_stats(task: Task) -> None:
        try:
            total = count_source_files(task.source_dir, state.cfg.settings.ignore_patterns)
        except Exception:
            total = 0
        task.synced_files = total
        task.total_files = total
        save_config(state.cfg)
        root.after(0, refresh)

    def on_add():
        new_t = _add_or_edit_dialog(root, state, None)
        if new_t:
            state.cfg.tasks.append(new_t)
            save_config(state.cfg)
            refresh()

    def on_edit():
        tid = _select_task_id(tree)
        if tid is None:
            return
        _, task = _find_task(state.cfg, tid)
        if not task:
            return
        if state.is_running(tid):
            messagebox.showerror("提示", "运行中任务不可编辑")
            return
        updated = _add_or_edit_dialog(root, state, task)
        if updated:
            idx, _ = _find_task(state.cfg, tid)
            state.cfg.tasks[idx] = updated
            save_config(state.cfg)
            refresh()

    def on_delete():
        tid = _select_task_id(tree)
        if tid is None:
            return
        if state.is_running(tid):
            messagebox.showerror("提示", "运行中任务不可删除")
            return
        if messagebox.askokcancel("确认", "确定删除该任务？"):
            idx, _ = _find_task(state.cfg, tid)
            if idx >= 0:
                state.cfg.tasks.pop(idx)
                save_config(state.cfg)
                refresh()

    def _validate_task(task: Task) -> bool:
        if not os.path.isdir(task.source_dir):
            messagebox.showerror("提示", "源目录不存在")
            return False
        for td in task.target_dirs:
            if not os.path.isdir(td):
                messagebox.showerror("提示", f"目标目录不存在: {td}")
                return False
        return True

    def on_full():
        tid = _select_task_id(tree)
        if tid is None:
            return
        _, task = _find_task(state.cfg, tid)
        if not task:
            return
        if not _validate_task(task):
            return

        def run_full():
            logger(f"开始完整同步: {task.name}")
            for td in task.target_dirs:
                try:
                    c, s, d = full_sync_once(
                        task.source_dir,
                        td,
                        state.cfg.settings.ignore_patterns,
                        on_copy=lambda s_path, d_path: logger(f"复制: {s_path} -> {d_path}")
                    )
                    logger(f"完成: {td} 复制{c} 跳过{s} 删除{d}")
                except Exception as exc:
                    logger(f"错误: {exc}")
            logger("完整同步结束")
            record_stats(task)

        threading.Thread(target=run_full, daemon=True).start()

    def on_start():
        tid = _select_task_id(tree)
        if tid is None:
            return
        idx, task = _find_task(state.cfg, tid)
        if not task or state.is_running(tid):
            return
        if not _validate_task(task):
            return

        stop_evt = threading.Event()
        state.running_threads[tid] = stop_evt
        task.running = True
        refresh()

        def boot():
            logger(f"启动任务: {task.name}")
            for td in task.target_dirs:
                try:
                    c, s, d = full_sync_once(
                        task.source_dir,
                        td,
                        state.cfg.settings.ignore_patterns,
                        on_copy=lambda s_path, d_path: logger(f"复制: {s_path} -> {d_path}")
                    )
                    logger(f"初次同步完成: {td} 复制{c} 跳过{s} 删除{d}")
                except Exception as exc:
                    logger(f"错误: {exc}")
            record_stats(task)

            def on_stats(total: int) -> None:
                task.synced_files = total
                task.total_files = total
                save_config(state.cfg)
                root.after(0, refresh)

            start_realtime_sync(task.source_dir, task.target_dirs, state.cfg.settings.ignore_patterns, stop_evt, logger, on_stats=on_stats)

        threading.Thread(target=boot, daemon=True).start()

    def on_stop():
        tid = _select_task_id(tree)
        if tid is None:
            return
        if not state.is_running(tid):
            return
        _, task = _find_task(state.cfg, tid)
        if task:
            logger(f"停止任务: {task.name}")
        logger(f"[ON_STOP] 设置 stop_event，task_id={tid}")
        state.running_threads[tid].set()
        logger(f"[ON_STOP] stop_event 已设置")
        del state.running_threads[tid]
        refresh()

    def on_settings():
        win = tk.Toplevel(root)
        win.title("设置")
        win.grab_set()
        txt = tk.Text(win, width=60, height=10)
        txt.insert("1.0", "\n".join(state.cfg.settings.ignore_patterns))
        txt.pack(padx=8, pady=8)
        def on_ok():
            new_lines = [line.strip() for line in txt.get("1.0", tk.END).splitlines() if line.strip()]
            state.cfg.settings.ignore_patterns = new_lines
            save_config(state.cfg)
            win.destroy()
        ttk.Button(win, text="保存", command=on_ok).pack(pady=6)

    refresh()
    root.mainloop()
